/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.corext.refactoring.code; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.text.edits.TextEditGroup; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor; import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.NamingConventions; import org.eclipse.jdt.core.SourceRange; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.EnumDeclaration; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IExtendedModifier; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.Initializer; import org.eclipse.jdt.core.dom.Javadoc; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NullLiteral; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SwitchCase; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext; import org.eclipse.jdt.core.dom.rewrite.ListRewrite; import org.eclipse.jdt.core.refactoring.CompilationUnitChange; import org.eclipse.jdt.core.refactoring.IJavaRefactorings; import org.eclipse.jdt.core.refactoring.descriptors.ExtractConstantDescriptor; import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor; import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory; import org.eclipse.jdt.internal.corext.Corext; import org.eclipse.jdt.internal.corext.SourceRangeFactory; import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility; import org.eclipse.jdt.internal.corext.dom.ASTNodes; import org.eclipse.jdt.internal.corext.dom.Bindings; import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer; import org.eclipse.jdt.internal.corext.dom.fragments.ASTFragmentFactory; import org.eclipse.jdt.internal.corext.dom.fragments.IASTFragment; import org.eclipse.jdt.internal.corext.dom.fragments.IExpressionFragment; import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel; import org.eclipse.jdt.internal.corext.fix.LinkedProposalPositionGroup; import org.eclipse.jdt.internal.corext.refactoring.Checks; import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment; import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments; import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil; import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; import org.eclipse.jdt.internal.corext.refactoring.base.JavaStringStatusContext; import org.eclipse.jdt.internal.corext.refactoring.base.RefactoringStatusCodes; import org.eclipse.jdt.internal.corext.refactoring.codingspectator.WatchedJavaRefactoring; import org.eclipse.jdt.internal.corext.refactoring.rename.RefactoringAnalyzeUtil; import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite; import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; import org.eclipse.jdt.internal.corext.util.JdtFlags; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.ui.CodeGeneration; import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider; import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesSettings; import org.eclipse.jdt.internal.ui.text.correction.ASTResolving; import org.eclipse.jdt.internal.ui.text.correction.ModifierCorrectionSubProcessor; import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels; /** * @author Mohsen Vakilian, nchen - Provided a method to create a refactoring descriptor. */ public class ExtractConstantRefactoring extends WatchedJavaRefactoring { /** * CODINGSPECTATOR: Made the attributes of the refactoring descriptor of extract constant public */ public static final String ATTRIBUTE_REPLACE= "replace"; //$NON-NLS-1$ public static final String ATTRIBUTE_QUALIFY= "qualify"; //$NON-NLS-1$ public static final String ATTRIBUTE_VISIBILITY= "visibility"; //$NON-NLS-1$ public static final String MODIFIER= "static final"; //$NON-NLS-1$ public static final String KEY_NAME= "name"; //$NON-NLS-1$ public static final String KEY_TYPE= "type"; //$NON-NLS-1$ private CompilationUnitRewrite fCuRewrite; private int fSelectionStart; private int fSelectionLength; private ICompilationUnit fCu; private IExpressionFragment fSelectedExpression; private Type fConstantTypeCache; private boolean fReplaceAllOccurrences= true; //default value private boolean fQualifyReferencesWithDeclaringClassName= false; //default value private String fVisibility= JdtFlags.VISIBILITY_STRING_PRIVATE; //default value private boolean fTargetIsInterface= false; private String fConstantName; private String[] fExcludedVariableNames; private boolean fSelectionAllStaticFinal; private boolean fAllStaticFinalCheckPerformed= false; //Constant Declaration Location private BodyDeclaration fToInsertAfter; private boolean fInsertFirst; private CompilationUnitChange fChange; private String[] fGuessedConstNames; private LinkedProposalModel fLinkedProposalModel; private boolean fCheckResultForCompileProblems; /** * Creates a new extract constant refactoring * * @param unit the compilation unit, or <code>null</code> if invoked by scripting * @param selectionStart start * @param selectionLength length */ public ExtractConstantRefactoring(ICompilationUnit unit, int selectionStart, int selectionLength) { Assert.isTrue(selectionStart >= 0); Assert.isTrue(selectionLength >= 0); fSelectionStart= selectionStart; fSelectionLength= selectionLength; fCu= unit; fCuRewrite= null; fLinkedProposalModel= null; fConstantName= ""; //$NON-NLS-1$ fCheckResultForCompileProblems= true; } public ExtractConstantRefactoring(CompilationUnit astRoot, int selectionStart, int selectionLength) { Assert.isTrue(selectionStart >= 0); Assert.isTrue(selectionLength >= 0); Assert.isTrue(astRoot.getTypeRoot() instanceof ICompilationUnit); fSelectionStart= selectionStart; fSelectionLength= selectionLength; fCu= (ICompilationUnit) astRoot.getTypeRoot(); fCuRewrite= new CompilationUnitRewrite(fCu, astRoot); fLinkedProposalModel= null; fConstantName= ""; //$NON-NLS-1$ fCheckResultForCompileProblems= true; } public ExtractConstantRefactoring(JavaRefactoringArguments arguments, RefactoringStatus status) { this((ICompilationUnit) null, 0, 0); RefactoringStatus initializeStatus= initialize(arguments); status.merge(initializeStatus); } public void setCheckResultForCompileProblems(boolean checkResultForCompileProblems) { fCheckResultForCompileProblems= checkResultForCompileProblems; } public void setLinkedProposalModel(LinkedProposalModel linkedProposalModel) { fLinkedProposalModel= linkedProposalModel; } @Override public String getName() { return RefactoringCoreMessages.ExtractConstantRefactoring_name; } public boolean replaceAllOccurrences() { return fReplaceAllOccurrences; } public void setReplaceAllOccurrences(boolean replaceAllOccurrences) { fReplaceAllOccurrences= replaceAllOccurrences; } public void setVisibility(String am) { Assert.isTrue( am == JdtFlags.VISIBILITY_STRING_PRIVATE || am == JdtFlags.VISIBILITY_STRING_PROTECTED || am == JdtFlags.VISIBILITY_STRING_PACKAGE || am == JdtFlags.VISIBILITY_STRING_PUBLIC ); fVisibility= am; } public String getVisibility() { return fVisibility; } public boolean getTargetIsInterface() { return fTargetIsInterface; } public boolean qualifyReferencesWithDeclaringClassName() { return fQualifyReferencesWithDeclaringClassName; } public void setQualifyReferencesWithDeclaringClassName(boolean qualify) { fQualifyReferencesWithDeclaringClassName= qualify; } public String guessConstantName() { String[] proposals= guessConstantNames(); if (proposals.length > 0) return proposals[0]; else return fConstantName; } /** * @return proposed variable names (may be empty, but not null). The first proposal should be * used as "best guess" (if it exists). */ public String[] guessConstantNames() { if (fGuessedConstNames == null) { try { Expression expression= getSelectedExpression().getAssociatedExpression(); if (expression != null) { ITypeBinding binding= guessBindingForReference(expression); fGuessedConstNames= StubUtility.getVariableNameSuggestions(NamingConventions.VK_STATIC_FINAL_FIELD, fCu.getJavaProject(), binding, expression, Arrays.asList(getExcludedVariableNames())); } } catch (JavaModelException e) { } if (fGuessedConstNames == null) fGuessedConstNames= new String[0]; } return fGuessedConstNames; } private String[] getExcludedVariableNames() { if (fExcludedVariableNames == null) { try { IExpressionFragment expr= getSelectedExpression(); Collection<String> takenNames= new ScopeAnalyzer(fCuRewrite.getRoot()).getUsedVariableNames(expr.getStartPosition(), expr.getLength()); fExcludedVariableNames= takenNames.toArray(new String[takenNames.size()]); } catch (JavaModelException e) { fExcludedVariableNames= new String[0]; } } return fExcludedVariableNames; } //CODINGSPECTATOR: Captured the cases where the refactoring is unavailable. @Override public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException { try { pm.beginTask("", 7); //$NON-NLS-1$ RefactoringStatus result= Checks.validateEdit(fCu, getValidationContext()); if (result.hasFatalError()) { //CODINGSPECTATOR logUnavailableRefactoring(result); return result; } pm.worked(1); if (fCuRewrite == null) { CompilationUnit cuNode= RefactoringASTParser.parseWithASTProvider(fCu, true, new SubProgressMonitor(pm, 3)); fCuRewrite= new CompilationUnitRewrite(fCu, cuNode); } else { pm.worked(3); } result.merge(checkSelection(new SubProgressMonitor(pm, 3))); if (result.hasFatalError()) { //CODINGSPECTATOR logUnavailableRefactoring(result); return result; } if (isLiteralNodeSelected()) fReplaceAllOccurrences= false; if (isInTypeDeclarationAnnotation(getSelectedExpression().getAssociatedNode())) { fVisibility= JdtFlags.VISIBILITY_STRING_PACKAGE; } ITypeBinding targetType= getContainingTypeBinding(); if (targetType.isInterface()) { fTargetIsInterface= true; fVisibility= JdtFlags.VISIBILITY_STRING_PUBLIC; } return result; } finally { pm.done(); } } public boolean selectionAllStaticFinal() { Assert.isTrue(fAllStaticFinalCheckPerformed); return fSelectionAllStaticFinal; } private void checkAllStaticFinal() throws JavaModelException { fSelectionAllStaticFinal= ConstantChecks.isStaticFinalConstant(getSelectedExpression()); fAllStaticFinalCheckPerformed= true; } private RefactoringStatus checkSelection(IProgressMonitor pm) throws JavaModelException { try { pm.beginTask("", 2); //$NON-NLS-1$ IExpressionFragment selectedExpression= getSelectedExpression(); if (selectedExpression == null) { String message= RefactoringCoreMessages.ExtractConstantRefactoring_select_expression; return CodeRefactoringUtil.checkMethodSyntaxErrors(fSelectionStart, fSelectionLength, fCuRewrite.getRoot(), message); } pm.worked(1); RefactoringStatus result= new RefactoringStatus(); result.merge(checkExpression()); if (result.hasFatalError()) return result; pm.worked(1); return result; } finally { pm.done(); } } private RefactoringStatus checkExpressionBinding() throws JavaModelException { return checkExpressionFragmentIsRValue(); } private RefactoringStatus checkExpressionFragmentIsRValue() throws JavaModelException { /* Moved this functionality to Checks, to allow sharing with ExtractTempRefactoring, others */ switch (Checks.checkExpressionIsRValue(getSelectedExpression().getAssociatedExpression())) { case Checks.NOT_RVALUE_MISC: return RefactoringStatus.createStatus(RefactoringStatus.FATAL, RefactoringCoreMessages.ExtractConstantRefactoring_select_expression, null, Corext.getPluginId(), RefactoringStatusCodes.EXPRESSION_NOT_RVALUE, null); case Checks.NOT_RVALUE_VOID: return RefactoringStatus.createStatus(RefactoringStatus.FATAL, RefactoringCoreMessages.ExtractConstantRefactoring_no_void, null, Corext.getPluginId(), RefactoringStatusCodes.EXPRESSION_NOT_RVALUE_VOID, null); case Checks.IS_RVALUE_GUESSED: case Checks.IS_RVALUE: return new RefactoringStatus(); default: Assert.isTrue(false); return null; } } private ITypeBinding guessBindingForReference(Expression expression) { ITypeBinding binding= expression.resolveTypeBinding(); if (binding == null) { binding= ASTResolving.guessBindingForReference(expression); } return binding; } // !!! -- same as in ExtractTempRefactoring private boolean isLiteralNodeSelected() throws JavaModelException { IExpressionFragment fragment= getSelectedExpression(); if (fragment == null) return false; Expression expression= fragment.getAssociatedExpression(); if (expression == null) return false; switch (expression.getNodeType()) { case ASTNode.BOOLEAN_LITERAL: case ASTNode.CHARACTER_LITERAL: case ASTNode.NULL_LITERAL: case ASTNode.NUMBER_LITERAL: return true; default: return false; } } private RefactoringStatus checkExpression() throws JavaModelException { RefactoringStatus result= new RefactoringStatus(); result.merge(checkExpressionBinding()); if (result.hasFatalError()) return result; checkAllStaticFinal(); IExpressionFragment selectedExpression= getSelectedExpression(); Expression associatedExpression= selectedExpression.getAssociatedExpression(); if (associatedExpression instanceof NullLiteral) result.merge(RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_null_literals)); else if (!ConstantChecks.isLoadTimeConstant(selectedExpression)) result.merge(RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_not_load_time_constant)); else if (associatedExpression instanceof SimpleName) { if (associatedExpression.getParent() instanceof QualifiedName && associatedExpression.getLocationInParent() == QualifiedName.NAME_PROPERTY || associatedExpression.getParent() instanceof FieldAccess && associatedExpression.getLocationInParent() == FieldAccess.NAME_PROPERTY) return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_select_expression); } return result; } public void setConstantName(String newName) { Assert.isNotNull(newName); fConstantName= newName; } public String getConstantName() { return fConstantName; } /** * This method performs checks on the constant name which are quick enough to be performed every * time the ui input component contents are changed. * * @return return the resulting status * @throws JavaModelException thrown when the operation could not be executed */ public RefactoringStatus checkConstantNameOnChange() throws JavaModelException { if (Arrays.asList(getExcludedVariableNames()).contains(fConstantName)) return RefactoringStatus.createErrorStatus(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_another_variable, BasicElementLabels.getJavaElementName(getConstantName()))); return Checks.checkConstantName(fConstantName, fCu); } // !! similar to ExtractTempRefactoring equivalent public String getConstantSignaturePreview() throws JavaModelException { String space= " "; //$NON-NLS-1$ return getVisibility() + space + MODIFIER + space + getConstantTypeName() + space + fConstantName; } public CompilationUnitChange createTextChange(IProgressMonitor pm) throws CoreException { createConstantDeclaration(); replaceExpressionsWithConstant(); return fCuRewrite.createChange(RefactoringCoreMessages.ExtractConstantRefactoring_change_name, true, pm); } @Override public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException { pm.beginTask(RefactoringCoreMessages.ExtractConstantRefactoring_checking_preconditions, 2); /* Note: some checks are performed on change of input widget * values. (e.g. see ExtractConstantRefactoring.checkConstantNameOnChange()) */ //TODO: possibly add more checking for name conflicts that might // lead to a change in behavior try { RefactoringStatus result= new RefactoringStatus(); createConstantDeclaration(); replaceExpressionsWithConstant(); fChange= fCuRewrite.createChange(RefactoringCoreMessages.ExtractConstantRefactoring_change_name, true, new SubProgressMonitor(pm, 1)); if (fCheckResultForCompileProblems) { checkSource(new SubProgressMonitor(pm, 1), result); } return result; } finally { fConstantTypeCache= null; fCuRewrite.clearASTAndImportRewrites(); pm.done(); } } private void checkSource(SubProgressMonitor monitor, RefactoringStatus result) throws CoreException { String newCuSource= fChange.getPreviewContent(new NullProgressMonitor()); CompilationUnit newCUNode= new RefactoringASTParser(ASTProvider.SHARED_AST_LEVEL).parse(newCuSource, fCu, true, true, monitor); IProblem[] newProblems= RefactoringAnalyzeUtil.getIntroducedCompileProblems(newCUNode, fCuRewrite.getRoot()); for (int i= 0; i < newProblems.length; i++) { IProblem problem= newProblems[i]; if (problem.isError()) result.addEntry(new RefactoringStatusEntry((problem.isError() ? RefactoringStatus.ERROR : RefactoringStatus.WARNING), problem.getMessage(), new JavaStringStatusContext(newCuSource, SourceRangeFactory.create(problem)))); } } private void createConstantDeclaration() throws CoreException { Type type= getConstantType(); IExpressionFragment fragment= getSelectedExpression(); Expression initializer= getSelectedExpression().createCopyTarget(fCuRewrite.getASTRewrite(), true); AST ast= fCuRewrite.getAST(); VariableDeclarationFragment variableDeclarationFragment= ast.newVariableDeclarationFragment(); variableDeclarationFragment.setName(ast.newSimpleName(fConstantName)); variableDeclarationFragment.setInitializer(initializer); FieldDeclaration fieldDeclaration= ast.newFieldDeclaration(variableDeclarationFragment); fieldDeclaration.setType(type); Modifier.ModifierKeyword accessModifier= Modifier.ModifierKeyword.toKeyword(fVisibility); if (accessModifier != null) fieldDeclaration.modifiers().add(ast.newModifier(accessModifier)); fieldDeclaration.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.STATIC_KEYWORD)); fieldDeclaration.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.FINAL_KEYWORD)); boolean createComments= JavaPreferencesSettings.getCodeGenerationSettings(fCu.getJavaProject()).createComments; if (createComments) { String comment= CodeGeneration.getFieldComment(fCu, getConstantTypeName(), fConstantName, StubUtility.getLineDelimiterUsed(fCu)); if (comment != null && comment.length() > 0) { Javadoc doc= (Javadoc) fCuRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JAVADOC); fieldDeclaration.setJavadoc(doc); } } AbstractTypeDeclaration parent= getContainingTypeDeclarationNode(); ListRewrite listRewrite= fCuRewrite.getASTRewrite().getListRewrite(parent, parent.getBodyDeclarationsProperty()); TextEditGroup msg= fCuRewrite.createGroupDescription(RefactoringCoreMessages.ExtractConstantRefactoring_declare_constant); if (insertFirst()) { listRewrite.insertFirst(fieldDeclaration, msg); } else { listRewrite.insertAfter(fieldDeclaration, getNodeToInsertConstantDeclarationAfter(), msg); } if (fLinkedProposalModel != null) { ASTRewrite rewrite= fCuRewrite.getASTRewrite(); LinkedProposalPositionGroup nameGroup= fLinkedProposalModel.getPositionGroup(KEY_NAME, true); nameGroup.addPosition(rewrite.track(variableDeclarationFragment.getName()), true); String[] nameSuggestions= guessConstantNames(); if (nameSuggestions.length > 0 && !nameSuggestions[0].equals(fConstantName)) { nameGroup.addProposal(fConstantName, null, nameSuggestions.length + 1); } for (int i= 0; i < nameSuggestions.length; i++) { nameGroup.addProposal(nameSuggestions[i], null, nameSuggestions.length - i); } LinkedProposalPositionGroup typeGroup= fLinkedProposalModel.getPositionGroup(KEY_TYPE, true); typeGroup.addPosition(rewrite.track(type), true); ITypeBinding typeBinding= guessBindingForReference(fragment.getAssociatedExpression()); if (typeBinding != null) { ITypeBinding[] relaxingTypes= ASTResolving.getNarrowingTypes(ast, typeBinding); for (int i= 0; i < relaxingTypes.length; i++) { typeGroup.addProposal(relaxingTypes[i], fCuRewrite.getCu(), relaxingTypes.length - i); } } boolean isInterface= parent.resolveBinding() != null && parent.resolveBinding().isInterface(); ModifierCorrectionSubProcessor.installLinkedVisibilityProposals(fLinkedProposalModel, rewrite, fieldDeclaration.modifiers(), isInterface); } } private Type getConstantType() throws JavaModelException { if (fConstantTypeCache == null) { IExpressionFragment fragment= getSelectedExpression(); ITypeBinding typeBinding= guessBindingForReference(fragment.getAssociatedExpression()); AST ast= fCuRewrite.getAST(); typeBinding= Bindings.normalizeForDeclarationUse(typeBinding, ast); ImportRewrite importRewrite= fCuRewrite.getImportRewrite(); ImportRewriteContext context= new ContextSensitiveImportRewriteContext(fCuRewrite.getRoot(), fSelectionStart, importRewrite); fConstantTypeCache= importRewrite.addImport(typeBinding, ast, context); } return fConstantTypeCache; } @Override public Change createChange(IProgressMonitor monitor) throws CoreException { ExtractConstantDescriptor descriptor= createRefactoringDescriptor(); fChange.setDescriptor(new RefactoringChangeDescriptor(descriptor)); return fChange; } private ExtractConstantDescriptor createRefactoringDescriptor() { final Map<String, String> arguments= new HashMap<String, String>(); String project= null; IJavaProject javaProject= fCu.getJavaProject(); if (javaProject != null) project= javaProject.getElementName(); int flags= JavaRefactoringDescriptor.JAR_REFACTORING | JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT; if (JdtFlags.getVisibilityCode(fVisibility) != Modifier.PRIVATE) flags|= RefactoringDescriptor.STRUCTURAL_CHANGE; final String expression= ASTNodes.asString(fSelectedExpression.getAssociatedExpression()); final String description= Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_descriptor_description_short, BasicElementLabels.getJavaElementName(fConstantName)); final String header= Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_descriptor_description, new String[] { BasicElementLabels.getJavaElementName(fConstantName), BasicElementLabels.getJavaCodeString(expression) }); final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header); comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_constant_name_pattern, BasicElementLabels.getJavaElementName(fConstantName))); comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_constant_expression_pattern, BasicElementLabels.getJavaCodeString(expression))); String visibility= fVisibility; if ("".equals(visibility)) //$NON-NLS-1$ visibility= RefactoringCoreMessages.ExtractConstantRefactoring_default_visibility; comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_visibility_pattern, visibility)); if (fReplaceAllOccurrences) comment.addSetting(RefactoringCoreMessages.ExtractConstantRefactoring_replace_occurrences); if (fQualifyReferencesWithDeclaringClassName) comment.addSetting(RefactoringCoreMessages.ExtractConstantRefactoring_qualify_references); arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT, JavaRefactoringDescriptorUtil.elementToHandle(project, fCu)); arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME, fConstantName); arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION, new Integer(fSelectionStart).toString() + " " + new Integer(fSelectionLength).toString()); //$NON-NLS-1$ arguments.put(ATTRIBUTE_REPLACE, Boolean.valueOf(fReplaceAllOccurrences).toString()); arguments.put(ATTRIBUTE_QUALIFY, Boolean.valueOf(fQualifyReferencesWithDeclaringClassName).toString()); arguments.put(ATTRIBUTE_VISIBILITY, new Integer(JdtFlags.getVisibilityCode(fVisibility)).toString()); ExtractConstantDescriptor descriptor= RefactoringSignatureDescriptorFactory.createExtractConstantDescriptor(project, description, comment.asString(), arguments, flags); return descriptor; } private void replaceExpressionsWithConstant() throws JavaModelException { ASTRewrite astRewrite= fCuRewrite.getASTRewrite(); AST ast= astRewrite.getAST(); IASTFragment[] fragmentsToReplace= getFragmentsToReplace(); for (int i= 0; i < fragmentsToReplace.length; i++) { IASTFragment fragment= fragmentsToReplace[i]; ASTNode node= fragment.getAssociatedNode(); boolean inTypeDeclarationAnnotation= isInTypeDeclarationAnnotation(node); if (inTypeDeclarationAnnotation && JdtFlags.VISIBILITY_STRING_PRIVATE == getVisibility()) continue; SimpleName ref= ast.newSimpleName(fConstantName); Name replacement= ref; boolean qualifyReference= qualifyReferencesWithDeclaringClassName(); if (!qualifyReference) { qualifyReference= inTypeDeclarationAnnotation; } if (qualifyReference) { replacement= ast.newQualifiedName(ast.newSimpleName(getContainingTypeBinding().getName()), ref); } TextEditGroup description= fCuRewrite.createGroupDescription(RefactoringCoreMessages.ExtractConstantRefactoring_replace); fragment.replace(astRewrite, replacement, description); if (fLinkedProposalModel != null) fLinkedProposalModel.getPositionGroup(KEY_NAME, true).addPosition(astRewrite.track(ref), false); } } private boolean isInTypeDeclarationAnnotation(ASTNode node) throws JavaModelException { ASTNode enclosingAnnotation= ASTNodes.getParent(node, Annotation.class); return enclosingAnnotation != null && enclosingAnnotation.getParent() == getContainingTypeDeclarationNode(); } private void computeConstantDeclarationLocation() throws JavaModelException { if (isDeclarationLocationComputed()) return; BodyDeclaration lastStaticDependency= null; Iterator<BodyDeclaration> decls= getContainingTypeDeclarationNode().bodyDeclarations().iterator(); while (decls.hasNext()) { BodyDeclaration decl= decls.next(); int modifiers; if (decl instanceof FieldDeclaration) modifiers= ((FieldDeclaration) decl).getModifiers(); else if (decl instanceof Initializer) modifiers= ((Initializer) decl).getModifiers(); else { continue; /* this declaration is not a field declaration or initializer, so the placement of the constant declaration relative to it does not matter */ } if (Modifier.isStatic(modifiers) && depends(getSelectedExpression(), decl)) lastStaticDependency= decl; } if (lastStaticDependency == null) fInsertFirst= true; else fToInsertAfter= lastStaticDependency; } /* bd is a static field declaration or static initializer */ private static boolean depends(IExpressionFragment selected, BodyDeclaration bd) { /* We currently consider selected to depend on bd only if db includes a declaration * of a static field on which selected depends. * * A more accurate strategy might be to also check if bd contains (or is) a * static initializer containing code which changes the value of a static field on * which selected depends. However, if a static is written to multiple times within * during class initialization, it is difficult to predict which value should be used. * This would depend on which value is used by expressions instances for which the new * constant will be substituted, and there may be many of these; in each, the * static field in question may have taken on a different value (if some of these uses * occur within static initializers). */ if (bd instanceof FieldDeclaration) { FieldDeclaration fieldDecl= (FieldDeclaration) bd; for (Iterator<VariableDeclarationFragment> fragments= fieldDecl.fragments().iterator(); fragments.hasNext();) { VariableDeclarationFragment fragment= fragments.next(); SimpleName staticFieldName= fragment.getName(); if (selected.getSubFragmentsMatching(ASTFragmentFactory.createFragmentForFullSubtree(staticFieldName)).length != 0) return true; } } return false; } private boolean isDeclarationLocationComputed() { return fInsertFirst == true || fToInsertAfter != null; } private boolean insertFirst() throws JavaModelException { if (!isDeclarationLocationComputed()) computeConstantDeclarationLocation(); return fInsertFirst; } private BodyDeclaration getNodeToInsertConstantDeclarationAfter() throws JavaModelException { if (!isDeclarationLocationComputed()) computeConstantDeclarationLocation(); return fToInsertAfter; } private String getConstantTypeName() throws JavaModelException { return ASTNodes.asString(getConstantType()); } private static boolean isStaticFieldOrStaticInitializer(BodyDeclaration node) { if (node instanceof MethodDeclaration || node instanceof AbstractTypeDeclaration) return false; int modifiers; if (node instanceof FieldDeclaration) { modifiers= ((FieldDeclaration) node).getModifiers(); } else if (node instanceof Initializer) { modifiers= ((Initializer) node).getModifiers(); } else { Assert.isTrue(false); return false; } if (!Modifier.isStatic(modifiers)) return false; return true; } /* * Elements returned by next() are BodyDeclaration or Annotation instances. */ private Iterator<ASTNode> getReplacementScope() throws JavaModelException { boolean declPredecessorReached= false; Collection<ASTNode> scope= new ArrayList<ASTNode>(); AbstractTypeDeclaration containingType= getContainingTypeDeclarationNode(); if (containingType instanceof EnumDeclaration) { // replace in all enum constants bodies EnumDeclaration enumDeclaration= (EnumDeclaration) containingType; scope.addAll(enumDeclaration.enumConstants()); } for (Iterator<IExtendedModifier> iter= containingType.modifiers().iterator(); iter.hasNext();) { IExtendedModifier modifier= iter.next(); if (modifier instanceof Annotation) { scope.add((ASTNode) modifier); } } for (Iterator<BodyDeclaration> bodyDeclarations= containingType.bodyDeclarations().iterator(); bodyDeclarations.hasNext();) { BodyDeclaration bodyDeclaration= bodyDeclarations.next(); if (bodyDeclaration == getNodeToInsertConstantDeclarationAfter()) declPredecessorReached= true; if (insertFirst() || declPredecessorReached || !isStaticFieldOrStaticInitializer(bodyDeclaration)) scope.add(bodyDeclaration); } return scope.iterator(); } private IASTFragment[] getFragmentsToReplace() throws JavaModelException { List<IASTFragment> toReplace= new ArrayList<IASTFragment>(); if (fReplaceAllOccurrences) { Iterator<ASTNode> replacementScope= getReplacementScope(); while (replacementScope.hasNext()) { ASTNode scope= replacementScope.next(); IASTFragment[] allMatches= ASTFragmentFactory.createFragmentForFullSubtree(scope).getSubFragmentsMatching(getSelectedExpression()); IASTFragment[] replaceableMatches= retainOnlyReplacableMatches(allMatches); for (int i= 0; i < replaceableMatches.length; i++) toReplace.add(replaceableMatches[i]); } } else if (canReplace(getSelectedExpression())) toReplace.add(getSelectedExpression()); return toReplace.toArray(new IASTFragment[toReplace.size()]); } // !! - like one in ExtractTempRefactoring private static IASTFragment[] retainOnlyReplacableMatches(IASTFragment[] allMatches) { List<IASTFragment> result= new ArrayList<IASTFragment>(allMatches.length); for (int i= 0; i < allMatches.length; i++) { if (canReplace(allMatches[i])) result.add(allMatches[i]); } return result.toArray(new IASTFragment[result.size()]); } // !! - like one in ExtractTempRefactoring private static boolean canReplace(IASTFragment fragment) { ASTNode node= fragment.getAssociatedNode(); ASTNode parent= node.getParent(); if (parent instanceof VariableDeclarationFragment) { VariableDeclarationFragment vdf= (VariableDeclarationFragment) parent; if (node.equals(vdf.getName())) return false; } if (parent instanceof ExpressionStatement) return false; if (parent instanceof SwitchCase) return false; return true; } private IExpressionFragment getSelectedExpression() throws JavaModelException { if (fSelectedExpression != null) return fSelectedExpression; IASTFragment selectedFragment= ASTFragmentFactory.createFragmentForSourceRange(new SourceRange(fSelectionStart, fSelectionLength), fCuRewrite.getRoot(), fCu); if (selectedFragment instanceof IExpressionFragment && !Checks.isInsideJavadoc(selectedFragment.getAssociatedNode())) { fSelectedExpression= (IExpressionFragment) selectedFragment; } if (fSelectedExpression != null && Checks.isEnumCase(fSelectedExpression.getAssociatedExpression().getParent())) { fSelectedExpression= null; } return fSelectedExpression; } /** * Returns the type to which the new constant will be added to. It is the first non-anonymous * parent. * * @return the type to add the new constant to * * @throws JavaModelException shouldn't happen */ private AbstractTypeDeclaration getContainingTypeDeclarationNode() throws JavaModelException { AbstractTypeDeclaration result= (AbstractTypeDeclaration) ASTNodes.getParent(getSelectedExpression().getAssociatedNode(), AbstractTypeDeclaration.class); Assert.isNotNull(result); return result; } private ITypeBinding getContainingTypeBinding() throws JavaModelException { ITypeBinding result= getContainingTypeDeclarationNode().resolveBinding(); Assert.isNotNull(result); return result; } private RefactoringStatus initialize(JavaRefactoringArguments arguments) { final String selection= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION); if (selection != null) { int offset= -1; int length= -1; final StringTokenizer tokenizer= new StringTokenizer(selection); if (tokenizer.hasMoreTokens()) offset= Integer.valueOf(tokenizer.nextToken()).intValue(); if (tokenizer.hasMoreTokens()) length= Integer.valueOf(tokenizer.nextToken()).intValue(); if (offset >= 0 && length >= 0) { fSelectionStart= offset; fSelectionLength= length; } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { selection, JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION })); } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION)); final String handle= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT); if (handle != null) { final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), handle, false); if (element == null || !element.exists() || element.getElementType() != IJavaElement.COMPILATION_UNIT) return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getName(), IJavaRefactorings.EXTRACT_CONSTANT); else fCu= (ICompilationUnit) element; } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT)); final String visibility= arguments.getAttribute(ATTRIBUTE_VISIBILITY); if (visibility != null && !"".equals(visibility)) {//$NON-NLS-1$ int flag= 0; try { flag= Integer.parseInt(visibility); } catch (NumberFormatException exception) { return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_VISIBILITY)); } fVisibility= JdtFlags.getVisibilityString(flag); } final String name= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME); if (name != null && !"".equals(name)) //$NON-NLS-1$ fConstantName= name; else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME)); final String replace= arguments.getAttribute(ATTRIBUTE_REPLACE); if (replace != null) { fReplaceAllOccurrences= Boolean.valueOf(replace).booleanValue(); } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_REPLACE)); final String declareFinal= arguments.getAttribute(ATTRIBUTE_QUALIFY); if (declareFinal != null) { fQualifyReferencesWithDeclaringClassName= Boolean.valueOf(declareFinal).booleanValue(); } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_QUALIFY)); return new RefactoringStatus(); } ///////////////// //CODINGSPECTATOR ///////////////// protected RefactoringDescriptor getOriginalRefactoringDescriptor() { return createRefactoringDescriptor(); } protected String getDescriptorID() { return IJavaRefactorings.EXTRACT_CONSTANT; } }